#pragma execution_character_set("utf-8")

#include "DataMgr.h"

#include <QDebug>
#include <QFile>
#include <QMessageBox>

#include "HttpPost.h"
#include "QApplication"
#include "QColor"
#include "QRegularExpression"

std::vector<std::pair<QDateTime, QDateTime>> getTimeSpans(int nNb)
{
    std::vector<std::pair<QDateTime, QDateTime>> timeSpans;

    QDateTime now = QDateTime::currentDateTime();
    QDateTime currentEnd;
    int currentDayOfWeek = now.date().dayOfWeek();

    int daysToSunday = 7 - currentDayOfWeek + 1;
    currentEnd = QDateTime(now.date().addDays(daysToSunday), QTime(0, 0, 0));

    for (int i = 0; i < nNb; ++i)
    {
        QDateTime endTime = currentEnd.addDays(-7 * i);
        //  auto s = currentEnd.toString("yyyy-MM-dd HH:mm:ss");
        // auto s1 = endTime.toString("yyyy-MM-dd HH:mm:ss");
        QDateTime startTime = endTime.addDays(-7);

        timeSpans.push_back(std::make_pair(startTime, endTime));
    }

    return timeSpans;
}

bool CFProblem::allIn(const std::set<QString>& setIn)
{
    if (setCFTag.size() == 0) return false;
    return std::all_of(setIn.begin(), setIn.end(),
                       [this](const QString& s)
                       { return setCFTag.find(s) != setCFTag.end(); });
}

bool CFProblem::allIn(const QStringList& lstProblem,
                      const std::set<QString>& setSearch)
{
    if (lstProblem.size() == 0) return false;
    return std::all_of(setSearch.begin(), setSearch.end(),
                       [&lstProblem](const QString& s)
                       { return lstProblem.indexOf(s) != -1; });
}

bool CFProblem::check() const
{
    return key.isValid() && !strProblemName.isEmpty() && nRating > 700;
}

QColor CFProblem::getColor() const
{
    if (nRating < 1200)
        return QColor(128, 128, 128);
    else if (nRating >= 1200 && nRating <= 1300)
        return QColor(0, 128, 0);
    else if (nRating > 1300 && nRating < 1600)
        return QColor(3, 168, 158);
    else if (nRating >= 1600 && nRating < 1900)
        return QColor(170, 0, 170);
    else if (nRating >= 1900 && nRating <= 2000)
        return QColor(188, 108, 178);
    else if (nRating > 2000 && nRating <= 2300)
        return QColor(255, 140, 0);
    else if (nRating >= 2400 && nRating <= 2599)
        return QColor(255, 0, 0);
    else if (nRating >= 2600 && nRating <= 2999)
        return QColor(200, 0, 0);

    return QColor(150, 0, 0);
}

QString CFProblem::getTags() const
{
    QString str;
    for (const auto& s : setCFTag)
    {
        str += (s + ",");
    }
    return str;
}

QString CFProblem::getURL() const
{
    return QString("https://codeforces.com/contest/%1/problem/%2")
        .arg(key.nContestID)
        .arg(key.strProblemIndex);
}

void DataMgr::loadDataFromLocal()
{
    CFLoadDataFromLocal();
    ATLoadDataFromLocal();
}

void DataMgr::CFSaveSpider()
{
    QJsonArray jarTmp;
    for (const auto& [_key, _tags] : m_mapCFLuoTag)
    {
        QJsonObject jsonTmp;
        jsonTmp.insert("nContestID", _key.nContestID);
        jsonTmp.insert("strProblemIndex", _key.strProblemIndex);

        QJsonArray jarTag;
        for (const auto& s : _tags)
        {
            jarTag.append(s);
        }
        jsonTmp.insert("tags", jarTag);
        jarTmp.push_back(jsonTmp);
    }
    writeJsonArray(m_strDataDir + "/spider-cf.json", jarTmp);
}

void DataMgr::ATSaveSpider()
{
    QJsonArray jarTmp;
    for (const auto& [_key, _tags] : m_mapATLuoTag)
    {
        QJsonObject jsonTmp;
        jsonTmp.insert("strContestID", _key.strContestID);
        jsonTmp.insert("strProblemID", _key.strProblemID);

        QJsonArray jarTag;
        for (const auto& s : _tags)
        {
            jarTag.append(s);
        }
        jsonTmp.insert("tags", jarTag);
        jsonTmp.insert("difficulty", m_mapATLuoDifficulty[_key]);
        jarTmp.push_back(jsonTmp);
    }
    writeJsonArray(m_strDataDir + "/spider-at.json", jarTmp);
}

void DataMgr::CFLoadDataFromLocal()
{
    m_mapCFLuoTag.clear();
    m_mapCFContest.clear();
    m_mapCFContestType.clear();
    m_vecCFAllTags.clear();
    m_setCFMyProblem.clear();

    // Tags
    QJsonArray jarTmp = readJsonArray(m_strDataDir + "/tags-cf.json");
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        QJsonObject jsonOne = jarTmp[i].toObject();
        if (jsonOne.value("en").toString().isEmpty())
        {
            continue;
        }

        auto en = jsonOne.value("en").toString();
        auto zh = jsonOne.value("zh").toString();
        if (zh.isEmpty())
        {
            zh = en;
        }

        AlgoTag tag = std::make_tuple(en, zh);
        m_vecCFAllTags.push_back(tag);
        m_mapTagZh[en] = zh;
    }

    QJsonObject jsonTmp =
        readJson(m_strDataDir + "/Codeforces/contestList.json");
    jarTmp = jsonTmp.value("result").toArray();
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        CFContestRef contest = std::make_shared<CFContest>();
        QJsonObject objContest = jarTmp[i].toObject();
        if (objContest.value("phase").toString() != "FINISHED")
        {
            continue;
        }

        contest->nContestID = objContest.value("id").toInt();
        contest->strContestName = objContest.value("name").toString();
        contest->dt = QDateTime::fromSecsSinceEpoch(
            objContest.value("startTimeSeconds").toInt());
        QString strTmp = contest->strContestName;
        strTmp.remove(QRegularExpression("\\s"));
        if (strTmp.contains("EducationalCodeforcesRound"))
        {
            contest->contestType = Edu;
        }
        else if (strTmp.contains("Div.2") && strTmp.contains("Div.1"))
        {
            contest->contestType = Div12;
        }
        else if (strTmp.contains("Div.1"))
        {
            contest->contestType = Div1;
        }
        else if (strTmp.contains("Div.2"))
        {
            contest->contestType = Div2;
        }
        else if (strTmp.contains("Div.3"))
        {
            contest->contestType = Div3;
        }
        else if (strTmp.contains("Div.4"))
        {
            contest->contestType = Div4;
        }
        else
        {
            contest->contestType = CF_Other;
        }

        if (!contest->check())
        {
            QMessageBox::information(nullptr, "Error", strTmp);
        }

        m_mapCFContest.emplace(contest->nContestID, contest);
    }

    jsonTmp = readJson(m_strDataDir + "/Codeforces/ProblemList.json");
    // Problem static
    std::unordered_map<CFProblemKey, int> mapSolved;
    jarTmp =
        jsonTmp.value("result").toObject().value("problemStatistics").toArray();
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        QJsonObject objP = jarTmp[i].toObject();
        CFProblemKey key;
        key.nContestID = objP.value("contestId").toInt();
        key.strProblemIndex = objP.value("index").toString();
        int nSolved = objP.value("solvedCount").toInt();

        mapSolved[key] = nSolved;
    }

    // Problem
    jarTmp = jsonTmp.value("result").toObject().value("problems").toArray();
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        CFProblemRef p = std::make_shared<CFProblem>();
        QJsonObject objP = jarTmp[i].toObject();
        if (!objP.contains("rating"))
        {
            continue;
        }

        p->key.nContestID = objP.value("contestId").toInt();
        p->key.strProblemIndex = objP.value("index").toString();
        p->nRating = objP.value("rating").toInt();
        p->strProblemName = objP.value("name").toString();

        QJsonArray jarTag = objP.value("tags").toArray();
        for (int j = 0; j < jarTag.size(); ++j)
        {
            p->setCFTag.insert(jarTag[j].toString());
        }
        if (p->setCFTag.size() == 0)
        {
            p->setCFTag.insert("unknown");
        }

        if (!p->check())
        {
            QMessageBox::information(nullptr, "Error", p->strProblemName);
        }

        if (m_mapCFContest.find(p->key.nContestID) == m_mapCFContest.end())
        {
            continue;
        }

        CFContestRef contest = m_mapCFContest[p->key.nContestID];
        if (contest->contestType == CF_Other)
        {
            //  continue;
        }

        // soloved count
        auto itSolved = mapSolved.find(p->key);
        if (itSolved != mapSolved.end())
        {
            p->nSolved = itSolved->second;
        }

        contest->vecProblem.push_back(p);
        m_mapCFContestType[contest->contestType].insert(contest->nContestID);
    }

    // my
    m_setCFMyProblem = CFReadOneUserProblem("weixirui");
    // other
    m_mapCFProblemToUser.clear();
    for (const auto& u : m_vecCFOtherUsers)
    {
        auto setOne = CFReadOneUserProblem(u);
        for (const auto& k : setOne)
        {
            m_mapCFProblemToUser[k].emplace(u);
        }
    }

    // spider
    jarTmp = readJsonArray(m_strDataDir + "/spider-cf.json");
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        CFProblemKey key;
        key.nContestID = jarTmp[i].toObject().value("nContestID").toInt();
        key.strProblemIndex =
            jarTmp[i].toObject().value("strProblemIndex").toString();

        const QJsonArray jarTag = jarTmp[i].toObject().value("tags").toArray();
        QStringList lstTag;
        for (int j = 0; j < jarTag.size(); ++j)
        {
            lstTag.push_back(jarTag[j].toString());
        }
        m_mapCFLuoTag[key] = lstTag;
    }
    // qDebug() << m_mapContest.size();
}

void DataMgr::ATLoadDataFromLocal()
{
    m_mapATLuoTag.clear();
    m_mapATLuoDifficulty.clear();
    m_mapATContest.clear();
    m_mapATContestType.clear();
    m_setATMyProblem.clear();
    m_mapATProblemToUser.clear();

    QJsonObject jsonTmp;
    QJsonArray jarTmp =
        readJsonArray(m_strDataDir + "/Atcoder/contestList.json");

    for (int i = 0; i < jarTmp.size(); ++i)
    {
        ATContestRef contest = std::make_shared<ATContest>();
        QJsonObject objContest = jarTmp[i].toObject();

        contest->strContestID = objContest.value("id").toString();
        contest->strContestName = objContest.value("title").toString();
        contest->dt = QDateTime::fromSecsSinceEpoch(
            objContest.value("start_epoch_second").toInt());
        QString strTmp = contest->strContestID.toUpper().left(3);
        if (strTmp == "ABC")
        {
            contest->contestType = AT_ABC;
        }
        else if (strTmp == "ARC")
        {
            contest->contestType = AT_ARC;
        }
        else if (strTmp == "AGC")
        {
            contest->contestType = AT_AGC;
        }
        else
        {
            continue;
        }

        if (!contest->check())
        {
            QMessageBox::information(nullptr, "Error", strTmp);
        }

        m_mapATContest.emplace(contest->strContestID, contest);
    }

    // Problem static
    QJsonObject jsonModel =
        readJson(m_strDataDir + "/Atcoder/problem-models.json");

    // Problem
    jarTmp = readJsonArray(m_strDataDir + "/Atcoder/problemList.json");
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        ATProblemRef p = std::make_shared<ATProblem>();
        QJsonObject objP = jarTmp[i].toObject();

        p->key.strContestID = objP.value("contest_id").toString();
        p->key.strProblemID = objP.value("id").toString();
        p->strProblemName = objP.value("name").toString();

        if (!p->check())
        {
            QMessageBox::information(nullptr, "Error", p->strProblemName);
        }

        if (m_mapATContest.find(p->key.strContestID) == m_mapATContest.end())
        {
            continue;
        }

        ATContestRef contest = m_mapATContest[p->key.strContestID];

        QJsonObject jsonDifficult =
            jsonModel.value(p->key.strProblemID).toObject();

        if (!jsonDifficult.contains("difficulty"))
        {
            continue;
        }

        p->nDifficult = jsonDifficult.value("difficulty").toInt();

        // difficult
        contest->vecProblem.push_back(p);
        m_mapATContestType[contest->contestType].insert(contest->strContestID);
    }

    // my
    m_setATMyProblem = ATReadOneUserProblem("weixirui");
    // other
    m_mapATProblemToUser.clear();
    for (const auto& u : m_vecATOtherUsers)
    {
        auto setOne = ATReadOneUserProblem(u);
        for (const auto& k : setOne)
        {
            m_mapATProblemToUser[k].emplace(u);
        }
    }

    // spider
    jarTmp = readJsonArray(m_strDataDir + "/spider-at.json");
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        ATProblemKey key;
        key.strContestID =
            jarTmp[i].toObject().value("strContestID").toString();
        key.strProblemID =
            jarTmp[i].toObject().value("strProblemID").toString();

        const QJsonArray jarTag = jarTmp[i].toObject().value("tags").toArray();
        QStringList lstTag;
        for (int j = 0; j < jarTag.size(); ++j)
        {
            lstTag.push_back(jarTag[j].toString());
        }
        m_mapATLuoTag[key] = lstTag;
        m_mapATLuoDifficulty[key] =
            jarTmp[i].toObject().value("difficulty").toInt();
    }
    // qDebug() << m_mapContest.size();
}

void DataMgr::parseLuoGuTag()
{
    m_vecLuoGuRootTag.clear();

    std::map<int, int> mapRoot;
    QJsonObject jsonTmp = readJson(m_strDataDir + "/tags-luogu.json");
    QJsonArray jarTmp = jsonTmp.value("tags").toArray();
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        int nType = jarTmp[i].toObject().value("type").toInt();
        if (nType != 2)
        {
            continue;
        }

        int nID = jarTmp[i].toObject().value("id").toInt();
        QString strName = jarTmp[i].toObject().value("name").toString();
        int nParent = jarTmp[i].toObject().value("parent").toInt();
        if (nParent == -1)
        {
            LuoGuTagRoot root;
            root.nID = nID;
            root.strName = strName;
            mapRoot[nID] = m_vecLuoGuRootTag.size();
            m_vecLuoGuRootTag.push_back(root);
        }
    }

    for (int i = 0; i < jarTmp.size(); ++i)
    {
        int nParent = jarTmp[i].toObject().value("parent").toInt();
        int nType = jarTmp[i].toObject().value("type").toInt();
        if (nType != 2 || nParent == -1)
        {
            continue;
        }

        int nID = jarTmp[i].toObject().value("id").toInt();
        QString strName = jarTmp[i].toObject().value("name").toString();
        LuoGuTag tag;
        tag.nID = nID;
        tag.strName = strName;
        auto it = mapRoot.find(nParent);
        if (it == mapRoot.end())
        {
            QMessageBox::information(nullptr, "Error", "Tag is rror.");
            return;
        }
        auto nIdx = it->second;
        m_vecLuoGuRootTag[nIdx].vecTag.push_back(tag);

        m_mapTagZh[QString::number(nID)] = strName;
    }

    // root 追加上
    for (auto& r : m_vecLuoGuRootTag)
    {
        LuoGuTag tag;
        tag.nID = r.nID;
        tag.strName = r.strName;
        r.vecTag.insert(r.vecTag.begin(), tag);

        m_mapTagZh[QString::number(r.nID)] = r.strName;
    }
}

std::set<QString> DataMgr::getSearchTag()
{
    if (m_bSearchCF && m_bUseCFTag)
    {
        return m_setCFSearchTag;
    }

    else
    {
        return m_setLuoGuSearchTag;
    }
}

DataMgr::DataMgr() { m_strDataDir = QApplication::applicationDirPath(); }

int DataMgr::getWeekIdx(const QDateTime& dt)
{
    auto it = std::find_if(m_vecWeeks.begin(), m_vecWeeks.end(),
                           [&dt](const auto& d)
                           { return d.first <= dt && d.second >= dt; });

    if (it != m_vecWeeks.end())
    {
        return std::distance(m_vecWeeks.begin(), it);
    }
    return -1;
}

std::map<ContestType, SearchContestInfo>& DataMgr::searchContestInfo()
{
    return m_mapSearchContest;
}

int DataMgr::getContestNb(ContestType type)
{
    if (isAT(type))
    {
        return m_mapATContestType[type].size();
    }
    return m_mapCFContestType[type].size();
}

CFContestRef DataMgr::getContest(CFContestID id) const
{
    auto it = m_mapCFContest.find(id);
    if (it == m_mapCFContest.end())
    {
        return {};
    }

    return it->second;
}

ATContestRef DataMgr::getContest(ATContestID id) const
{
    auto it = m_mapATContest.find(id);
    if (it == m_mapATContest.end())
    {
        return {};
    }

    return it->second;
}

std::vector<CFSearchOneProblem> DataMgr::searchCF()
{
    const std::set<QString> setSearch = getSearchTag();
    if (setSearch.size() == 0)
    {
        //  return {};
    }

    std::vector<CFSearchOneProblem> vecRet;

    for (const auto& [_type, _info] : m_mapSearchContest)
    {
        if (isAT(_type))
        {
            continue;
        }

        if (!m_mapSearchContest[_type].bChecked)
        {
            continue;
        }

        const std::set<CFContestID> setContestID = m_mapCFContestType[_type];
        int nCount = _info.nNbOfContest;
        if (nCount < 0)
        {
            nCount = setContestID.size();
        }

        int nNb = 0;
        auto it = setContestID.rbegin();
        for (; it != setContestID.rend(); ++it)
        {
            ++nNb;
            if (nNb >= nCount)
            {
                break;
            }

            CFContestRef contest = getContest(*it);
            if (contest == nullptr)
            {
                continue;
            }

            const std::vector<CFProblemRef>& vecProblem = contest->vecProblem;
            for (const auto& p : vecProblem)
            {
                if (p->nRating >= m_nRatingMin && p->nRating <= m_nRatingMax)
                {
                    bool bOk = false;
                    std::vector<QString> vecTag;
                    if (m_bUseCFTag)
                    {
                        if (setSearch.size() == 0 || p->allIn(setSearch))
                        {
                            vecTag = std::vector<QString>{p->setCFTag.begin(),
                                                          p->setCFTag.end()};
                            bOk = true;
                        }
                    }
                    else
                    {
                        const QStringList& lstTag = m_mapCFLuoTag[p->key];
                        if (setSearch.size() == 0 ||
                            p->allIn(lstTag, setSearch))
                        {
                            vecTag = std::vector<QString>{lstTag.begin(),
                                                          lstTag.end()};
                            bOk = true;
                        }
                    }

                    if (bOk)
                    {
                        CFSearchOneProblem cfOne;
                        cfOne.problemRef = p;
                        cfOne.contestRef = contest;
                        cfOne.vecTag = vecTag;
                        vecRet.push_back(cfOne);
                    }
                }
            }
        }
    }

    return vecRet;
}
std::vector<CFSearchSomeOne> DataMgr::searchCFSomeOne(const QString& strUser)
{
    const std::set<QString> setSearch = getSearchTag();
    if (setSearch.size() == 0)
    {
        //  return {};
    }

    const std::vector<std::pair<QDateTime, QDateTime>> vecWeeks =
        getTimeSpans(1000);
    auto getDataIdx = [&vecWeeks](const QDateTime& d)
    {
        int n = -1;
        for (int i = 0; i < vecWeeks.size(); ++i)
        {
            if (vecWeeks[i].first <= d && d <= vecWeeks[i].second)
            {
                n = i;
                break;
            }
        }
        return n;
    };

    std::map<CFProblemKey, CFSearchSomeOne> mapTmp;
    auto jsonTmp = readJson(m_strDataDir + "/Codeforces/" + strUser + ".json");
    auto jarTmp = jsonTmp.value("result").toArray();
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        QJsonObject jsoni = jarTmp.at(i).toObject();
        if (jsoni.value("verdict").toString() != "OK")
        {
            continue;
        }
        CFSearchSomeOne info;

        CFProblemKey key;
        key.nContestID =
            jsoni.value("problem").toObject().value("contestId").toInt();
        key.strProblemIndex =
            jsoni.value("problem").toObject().value("index").toString();
        info.dtSubmit = QDateTime::fromSecsSinceEpoch(
            jsoni.value("creationTimeSeconds").toInt());
        info.nWeekIndex = getDataIdx(info.dtSubmit);
        mapTmp.emplace(key, info);
    }

    std::vector<CFSearchSomeOne> vecRet;

    for (const auto& [_type, _info] : m_mapSearchContest)
    {
        if (isAT(_type))
        {
            continue;
        }

        const std::set<CFContestID> setContestID = m_mapCFContestType[_type];
        auto it = setContestID.rbegin();
        for (; it != setContestID.rend(); ++it)
        {
            CFContestRef contest = getContest(*it);
            if (contest == nullptr)
            {
                continue;
            }

            const std::vector<CFProblemRef>& vecProblem = contest->vecProblem;
            for (const auto& p : vecProblem)
            {
                auto itTmp = mapTmp.find(p->key);
                if (itTmp != mapTmp.end())
                {
                    std::vector<QString> vecTag;
                    if (m_bUseCFTag)
                    {
                        vecTag = std::vector<QString>{p->setCFTag.begin(),
                                                      p->setCFTag.end()};
                    }
                    else
                    {
                        const QStringList& lstTag = m_mapCFLuoTag[p->key];
                        vecTag =
                            std::vector<QString>{lstTag.begin(), lstTag.end()};
                    }

                    CFSearchSomeOne one;
                    one.problemRef = p;
                    one.contestRef = contest;
                    one.vecTag = vecTag;
                    one.dtSubmit = itTmp->second.dtSubmit;
                    one.nWeekIndex = itTmp->second.nWeekIndex;
                    vecRet.push_back(one);
                }
            }
        }
    }

    std::sort(vecRet.begin(), vecRet.end());

    return vecRet;
}

std::vector<ATSearchOneProblem> DataMgr::searchAT()
{
    const std::set<QString> setSearch = getSearchTag();
    if (setSearch.size() == 0)
    {
        //  return {};
    }

    std::vector<ATSearchOneProblem> vecRet;

    for (const auto& [_type, _info] : m_mapSearchContest)
    {
        if (!isAT(_type))
        {
            continue;
        }

        if (!m_mapSearchContest[_type].bChecked)
        {
            continue;
        }

        const std::set<ATContestID> setContestID = m_mapATContestType[_type];
        int nCount = _info.nNbOfContest;
        if (nCount < 0)
        {
            nCount = setContestID.size();
        }

        int nNb = 0;
        auto it = setContestID.rbegin();
        for (; it != setContestID.rend(); ++it)
        {
            ++nNb;
            if (nNb >= nCount)
            {
                break;
            }

            ATContestRef contest = getContest(*it);
            if (contest == nullptr)
            {
                continue;
            }

            const std::vector<ATProblemRef>& vecProblem = contest->vecProblem;
            for (const auto& p : vecProblem)
            {
                const int nDifficult = m_mapATLuoDifficulty[p->key];
                if (nDifficult >= m_nRatingMin && nDifficult <= m_nRatingMax)
                {
                    std::vector<QString> vecTag;
                    const QStringList& lstTag = m_mapATLuoTag[p->key];
                    if (setSearch.size() > 0)
                    {
                        if (!CFProblem::allIn(lstTag, setSearch))
                        {
                            continue;
                        }
                    }

                    vecTag = std::vector<QString>{lstTag.begin(), lstTag.end()};

                    ATSearchOneProblem atOne;
                    atOne.problemRef = p;
                    atOne.contestRef = contest;
                    atOne.vecTag = vecTag;
                    vecRet.push_back(atOne);
                }
            }
        }
    }

    return vecRet;
}

std::vector<ATSearchOneProblem> DataMgr::searchAT_FG(const QStringList& vecID)
{
    const std::set<QString> setSearch = getSearchTag();
    if (setSearch.size() == 0)
    {
        //  return {};
    }

    std::vector<ATSearchOneProblem> vecRet;

    for (const auto& [_type, _info] : m_mapSearchContest)
    {
        if (!isAT(_type))
        {
            continue;
        }

        if (!m_mapSearchContest[_type].bChecked)
        {
            continue;
        }

        const std::set<ATContestID> setContestID = m_mapATContestType[_type];
        int nCount = _info.nNbOfContest;
        if (nCount < 0)
        {
            nCount = setContestID.size();
        }

        int nNb = 0;
        auto it = setContestID.rbegin();
        for (; it != setContestID.rend(); ++it)
        {
            ++nNb;
            if (nNb >= nCount)
            {
                break;
            }

            ATContestRef contest = getContest(*it);
            if (contest == nullptr)
            {
                continue;
            }

            const std::vector<ATProblemRef>& vecProblem = contest->vecProblem;
            for (const auto& p : vecProblem)
            {
                const int nDifficult = m_mapATLuoDifficulty[p->key];
                if (nDifficult >= m_nRatingMin && nDifficult <= m_nRatingMax)
                {
                    std::vector<QString> vecTag;
                    const QStringList& lstTag = m_mapATLuoTag[p->key];
                    if (setSearch.size() > 0)
                    {
                        if (!CFProblem::allIn(lstTag, setSearch))
                        {
                            continue;
                        }
                    }

                    const QString strTmp = p->key.strProblemID.right(1);

                    if (vecID.indexOf(strTmp) == -1)
                    {
                        continue;
                    }

                    vecTag = std::vector<QString>{lstTag.begin(), lstTag.end()};

                    ATSearchOneProblem atOne;
                    atOne.problemRef = p;
                    atOne.contestRef = contest;
                    atOne.vecTag = vecTag;
                    vecRet.push_back(atOne);
                }
            }
        }
    }

    return vecRet;
}

DataMgr* DataMgr::getInstance()
{
    static DataMgr s_dm;
    return &s_dm;
}

std::tuple<QString, QString> DataMgr::getTags()
{
    QString str1;
    for (auto s : m_setLuoGuSearchTag) str1 += (m_mapTagZh[s] + "<br>");

    QString str2;
    for (auto s : m_setCFSearchTag) str2 += (m_mapTagZh[s] + "<br>");

    return std::make_tuple(str1, str2);
}

std::vector<CFProblemKey> DataMgr::getCFProblemKey()
{
    std::vector<CFProblemKey> vecRet;

    std::vector<CFContestID> vecContest;

    for (const auto& [_type, _info] : m_mapSearchContest)
    {
        if (isAT(_type))
        {
            continue;
        }

        const std::set<CFContestID> setContestID = m_mapCFContestType[_type];
        vecContest.insert(vecContest.begin(), setContestID.begin(),
                          setContestID.end());
    }
    std::sort(vecContest.begin(), vecContest.end(),
              [this](CFContestID a, CFContestID b)
              { return m_mapCFContest[a]->dt > m_mapCFContest[b]->dt; });

    for (const auto id : vecContest)
    {
        CFContestRef contest = getContest(id);
        if (contest == nullptr)
        {
            continue;
        }

        const std::vector<CFProblemRef>& vecProblem = contest->vecProblem;
        for (const auto& p : vecProblem)
        {
            vecRet.push_back(p->key);
        }
    }

    return vecRet;
}

std::vector<ATProblemKey> DataMgr::getATProblemKey()
{
    std::vector<ATProblemKey> vecRet;

    std::vector<ATContestID> vecContest;

    for (const auto& [_type, _info] : m_mapSearchContest)
    {
        if (!isAT(_type))
        {
            continue;
        }

        const std::set<ATContestID> setContestID = m_mapATContestType[_type];
        vecContest.insert(vecContest.begin(), setContestID.begin(),
                          setContestID.end());
    }
    std::sort(vecContest.begin(), vecContest.end(),
              [this](ATContestID a, ATContestID b)
              { return m_mapATContest[a]->dt > m_mapATContest[b]->dt; });

    for (const auto id : vecContest)
    {
        ATContestRef contest = getContest(id);
        if (contest == nullptr)
        {
            continue;
        }

        const std::vector<ATProblemRef>& vecProblem = contest->vecProblem;
        for (const auto& p : vecProblem)
        {
            vecRet.push_back(p->key);
        }
    }

    return vecRet;
}

struct StaticsInfo
{
    CFProblemKey key;
    QDateTime dtCreate;
    QString strName;
    int nRating = 0;
    std::vector<QString> vecTag;

    QString getTags() const
    {
        QString s;
        for (auto& n : vecTag) s += (n + ", ");
        return s;
    }

    bool operator<(const StaticsInfo& other) const
    {
        return strName < other.strName;
    }
};

std::vector<QString> DataMgr::getUserStatics(const QString& strUser)
{
    std::vector<QString> vecRet;

    auto jsonTmp = readJson(m_strDataDir + "/Codeforces/" + strUser + ".json");
    auto jarTmp = jsonTmp.value("result").toArray();
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        QJsonObject jsoni = jarTmp.at(i).toObject();
        if (jsoni.value("verdict").toString() != "OK")
        {
            continue;
        }
        StaticsInfo info;

        info.key.nContestID =
            jsoni.value("problem").toObject().value("contestId").toInt();
        info.key.strProblemIndex =
            jsoni.value("problem").toObject().value("index").toString();
        info.strName =
            jsoni.value("problem").toObject().value("name").toString();
        info.nRating =
            jsoni.value("problem").toObject().value("rating").toInt();

        info.dtCreate = QDateTime::fromSecsSinceEpoch(
            jsoni.value("creationTimeSeconds").toInt());

        QString strCType = "unknown";
        auto itFind = m_mapCFContest.find(info.key.nContestID);
        if (itFind != m_mapCFContest.end())
        {
            strCType = toString(itFind->second->contestType);
        }

        vecRet.push_back(QString("%1%2%3%4-%5")
                             .arg(info.key.toString(), -8)
                             .arg(strCType, -13)
                             .arg(info.nRating, -6)
                             .arg(info.dtCreate.toString("yyyy-MM-dd HH:mm:ss"))
                             .arg(info.strName));
    }

    return vecRet;
}

std::vector<QString> DataMgr::getUserStatics(int nStart, int nEnd)
{
    m_vecWeeks = getTimeSpans(1000);

    std::vector<QString> vecRet;

    for (int nWeek = nStart; nWeek <= nEnd; ++nWeek)
    {
        vecRet.push_back(
            QString("No.%1 (").arg(nWeek + 1) +
            m_vecWeeks[nWeek].first.toString("yyyy-MM-dd HH:mm:ss") + "~" +
            m_vecWeeks[nWeek].second.toString("yyyy-MM-dd HH:mm:ss") + ")");

        for (auto& strUser : m_vecCFOtherUsers)
        {
            std::vector<QString> vecTag;
            std::vector<StaticsInfo> vecResult;
            vecRet.push_back(QString("%1").arg(strUser, -20));

            auto jsonTmp =
                readJson(m_strDataDir + "/Codeforces/" + strUser + ".json");
            auto jarTmp = jsonTmp.value("result").toArray();
            for (int i = 0; i < jarTmp.size(); ++i)
            {
                QJsonObject jsoni = jarTmp.at(i).toObject();
                if (jsoni.value("verdict").toString() != "OK")
                {
                    continue;
                }
                StaticsInfo info;

                info.key.nContestID = jsoni.value("problem")
                                          .toObject()
                                          .value("contestId")
                                          .toInt();
                info.key.strProblemIndex =
                    jsoni.value("problem").toObject().value("index").toString();
                info.strName =
                    jsoni.value("problem").toObject().value("name").toString();
                info.nRating =
                    jsoni.value("problem").toObject().value("rating").toInt();

                // tag
                QJsonArray jarTag =
                    jsoni.value("problem").toObject().value("tags").toArray();
                for (auto& j : jarTag)
                {
                    info.vecTag.push_back(j.toString());
                }

                if (!info.key.isValid())
                {
                    continue;
                }

                info.dtCreate = QDateTime::fromSecsSinceEpoch(
                    jsoni.value("creationTimeSeconds").toInt());
                const int nIdx = getWeekIdx(info.dtCreate);
                if (nIdx == nWeek)
                {
                    vecResult.push_back(info);
                    vecTag.push_back(info.getTags());
                }
            }
            std::sort(vecResult.begin(), vecResult.end());
            auto itU = std::unique(vecResult.begin(), vecResult.end(),
                                   [](auto a, auto b)
                                   { return a.strName == b.strName; });
            vecResult.erase(itU, vecResult.end());

            for (auto& r : vecResult)
            {
                vecRet.push_back(
                    QString("%1%2%3-%4")
                        .arg(r.key.toString(), -10)
                        .arg(r.nRating, -6)
                        .arg(r.dtCreate.toString("yyyy-MM-dd HH:mm:ss"))
                        .arg(r.strName));
            }
            vecRet.push_back("\n");
            std::sort(vecTag.begin(), vecTag.end());
            for (auto& t : vecTag)
            {
                // vecRet.push_back(t);
            }
            // vecRet.push_back("\n");
        }
    }

    return vecRet;
}

void DataMgr::loadConfig()
{
    m_vecATOtherUsers.clear();
    m_vecCFOtherUsers.clear();
    m_mapSearchContest.clear();

    QJsonObject jsonTmp = readJson(m_strDataDir + "/Config.json");
    QJsonArray jarTmp = jsonTmp.value("ATOtherUser").toArray();
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        m_vecATOtherUsers.push_back(jarTmp[i].toString());
    }

    jarTmp = jsonTmp.value("CFOtherUser").toArray();
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        m_vecCFOtherUsers.push_back(jarTmp[i].toString());
    }

    parseLuoGuTag();

    QJsonObject jsonSearch = jsonTmp.value("Search").toObject();

    QJsonObject jsonOne;

    jsonOne = jsonSearch.value("Div1").toObject();
    m_mapSearchContest.emplace(
        Div1, SearchContestInfo{jsonOne.value("Nb").toInt(),
                                jsonOne.value("Checked").toBool()});

    jsonOne = jsonSearch.value("Div2").toObject();
    m_mapSearchContest.emplace(
        Div2, SearchContestInfo{jsonOne.value("Nb").toInt(),
                                jsonOne.value("Checked").toBool()});

    jsonOne = jsonSearch.value("Div3").toObject();
    m_mapSearchContest.emplace(
        Div3, SearchContestInfo{jsonOne.value("Nb").toInt(),
                                jsonOne.value("Checked").toBool()});

    jsonOne = jsonSearch.value("Div4").toObject();
    m_mapSearchContest.emplace(
        Div4, SearchContestInfo{jsonOne.value("Nb").toInt(),
                                jsonOne.value("Checked").toBool()});

    jsonOne = jsonSearch.value("Edu").toObject();
    m_mapSearchContest.emplace(
        Edu, SearchContestInfo{jsonOne.value("Nb").toInt(),
                               jsonOne.value("Checked").toBool()});

    jsonOne = jsonSearch.value("Div12").toObject();
    m_mapSearchContest.emplace(
        Div12, SearchContestInfo{jsonOne.value("Nb").toInt(),
                                 jsonOne.value("Checked").toBool()});

    // at
    jsonOne = jsonSearch.value("ABC").toObject();
    m_mapSearchContest.emplace(
        AT_ABC, SearchContestInfo{jsonOne.value("Nb").toInt(),
                                  jsonOne.value("Checked").toBool()});
    jsonOne = jsonSearch.value("ARC").toObject();
    m_mapSearchContest.emplace(
        AT_ARC, SearchContestInfo{jsonOne.value("Nb").toInt(),
                                  jsonOne.value("Checked").toBool()});
    jsonOne = jsonSearch.value("AGC").toObject();
    m_mapSearchContest.emplace(
        AT_AGC, SearchContestInfo{jsonOne.value("Nb").toInt(),
                                  jsonOne.value("Checked").toBool()});
}

QJsonObject DataMgr::readJson(const QString& strFile)
{
    QFile file(strFile);
    if (!file.open(QIODevice::ReadOnly))
    {
        qWarning() << "Could not open file:" << strFile;
        return QJsonObject();
    }

    QByteArray data = file.readAll();
    file.close();

    QJsonParseError parseError;
    QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);

    if (parseError.error != QJsonParseError::NoError)
    {
        qWarning() << "JSON parse error:" << parseError.errorString();
        return QJsonObject();
    }

    if (!doc.isObject())
    {
        qWarning() << "JSON document is not an object";
        return QJsonObject();
    }

    return doc.object();
}

void DataMgr::saveJson(const QString& strFile) { QJsonObject json; }

QJsonArray DataMgr::readJsonArray(const QString& strFile)
{
    QFile file(strFile);
    if (!file.open(QIODevice::ReadOnly))
    {
        qWarning() << "Could not open file:" << strFile;
        return QJsonArray();
    }

    QByteArray data = file.readAll();
    file.close();

    QJsonParseError parseError;
    QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);

    if (parseError.error != QJsonParseError::NoError)
    {
        qWarning() << "JSON parse error:" << parseError.errorString();
        return QJsonArray();
    }

    return doc.array();
}

void DataMgr::writeJsonArray(const QString& strFile, const QJsonArray& jar)
{
    QJsonDocument doc(jar);
    QByteArray jsonData =
        doc.toJson(QJsonDocument::Indented);  // 可选：使用 Indented 格式化输出

    QFile file(strFile);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        qWarning() << "Cannot open file for writing:" << strFile;
    }

    qint64 result = file.write(jsonData);
    file.close();

    if (result != jsonData.size())
    {
        qWarning() << "Failed to write all data to file:" << strFile;
    }
}

std::unordered_set<CFProblemKey> DataMgr::CFReadOneUserProblem(
    const QString& strUser)
{
    std::unordered_set<CFProblemKey> setRet;

    auto jsonTmp = readJson(m_strDataDir + "/Codeforces/" + strUser + ".json");
    auto jarTmp = jsonTmp.value("result").toArray();
    for (int i = 0; i < jarTmp.size(); ++i)
    {
        QJsonObject jsoni = jarTmp.at(i).toObject();
        if (jsoni.value("verdict").toString() != "OK")
        {
            continue;
        }
        CFProblemKey key;
        key.nContestID =
            jsoni.value("problem").toObject().value("contestId").toInt();
        key.strProblemIndex =
            jsoni.value("problem").toObject().value("index").toString();
        if (key.isValid())
        {
            setRet.emplace(key);
        }
    }
    return setRet;
}

std::unordered_set<ATProblemKey> DataMgr::ATReadOneUserProblem(
    const QString& strUser)
{
    std::unordered_set<ATProblemKey> setRet;

    auto jarTmp = readJsonArray(m_strDataDir + "/Atcoder/" + strUser + ".json");

    for (int i = 0; i < jarTmp.size(); ++i)
    {
        QJsonObject jsoni = jarTmp.at(i).toObject();
        if (jsoni.value("result").toString() != "AC")
        {
            continue;
        }
        ATProblemKey key;
        key.strContestID = jsoni.value("contest_id").toString();
        key.strProblemID = jsoni.value("problem_id").toString();
        if (key.isValid())
        {
            setRet.emplace(key);
        }
    }
    return setRet;
}

bool CFContest::check() const
{
    return !strContestName.isEmpty() && nContestID > 0;
}

bool ATProblem::check() const
{
    return key.isValid() && !strProblemName.isEmpty();
}

QColor ATProblem::getColor() const
{
    if (nDifficult <= 399)
        return Qt::gray;
    else if (nDifficult >= 400 && nDifficult <= 799)
        return QColor(128, 64, 0);
    else if (nDifficult >= 800 && nDifficult <= 1199)
        return QColor(0, 128, 0);
    else if (nDifficult >= 1200 && nDifficult <= 1599)
        return QColor(0, 192, 192);
    else if (nDifficult >= 1600 && nDifficult <= 1999)
        return Qt::blue;
    else if (nDifficult >= 2000 && nDifficult <= 2399)
        return Qt::yellow;
    else if (nDifficult >= 2400 && nDifficult <= 2799)
        return QColor(255, 128, 0);

    return Qt::red;
}

QString ATProblem::getURL() const
{
    return QString("https://atcoder.jp/contests/%1/tasks/%2")
        .arg(key.strContestID)
        .arg(key.strProblemID);
}

bool ATContest::check() const
{
    return !strContestName.isEmpty() && !strContestID.isEmpty();
}
